// states
var NONE = 0;
var SHIFT = 1;
var ZOOM = 2;


var KEY_LEFT = 37;
var KEY_UP = 38;
var KEY_RIGHT = 39;
var KEY_DOWN = 40;
var KEY_PGUP = 33;
var KEY_PGDOWN = 34;

var MIN_UNIT_SIZE = 3;
var _controller = null;

// roles
var PLAY_WHITE = 1;
var PLAY_BLACK = 2;
var WATCH = 3;
var UNKNOWN = 4;

function Validator() {
  this.enabled = true;
}

Validator.prototype.enable = function(e) {
  this.enabled = e;
}

Validator.prototype.isEnabled = function() {
  return this.enabled;
}



function RealOutput(view, canvasName) {
  this.view = view;
  this.jg = new jsGraphics(canvasName);
}

RealOutput.prototype.drawElement = function(x, y, xlen, ylen, color, swapped, winning, captured, swappable) {
  this.jg.setColor(swappable ? "#FF0000" : "#888888");
  this.jg.drawRect(x, y, xlen, ylen);
  this.jg.setColor(color == BLACK ? "#000000" : "#FFFFFF");
  if (winning) {
    this.jg.setColor(color == BLACK ? "#0F770F" : "#BBFFBB");
  }
  if (swapped) {
    this.jg.setColor(color == BLACK ? "#550F0F" : "#FFDDDD");
  }
  if (captured) {
    this.jg.setColor(color == BLACK ? "#DDDDFF" : "#0F0F55");
  }
  this.jg.fillRect(x+1, y+1, xlen-1, ylen-1);
}

RealOutput.prototype.drawEmpty = function(x, y, xlen, ylen) {
  this.jg.setColor("#888888");
  this.jg.fillRect(x, y, xlen, ylen);
}

RealOutput.prototype.start = function() {
  this.jg.clear();
}

RealOutput.prototype.flush = function() {
  this.jg.paint();

}

function ListOfMoves(elName) {
  this.elName = "#" + elName;
  this.moveIdViewed = 0;
  this.moveId = 0;
  this.refresh();
}

ListOfMoves.prototype.submitMove = function(moveId) {
  this.moveId = moveId - 1;
  this.moveIdViewed = 0;
  this.refresh();
}

ListOfMoves.prototype.undoMove = function(moveId) {
  this.moveId = moveId - 1;
  this.moveIdViewed = 0;
  this.refresh();
}

ListOfMoves.prototype.setMoveSelected = function(id) {
  this.moveIdViewed = id;
  this.refresh();
}

ListOfMoves.prototype.getViewedMoveId = function() {
  return this.moveIdViewed;
}

ListOfMoves.prototype.refresh = function() {
  var i = 1;

  var template = $('#moveHistoryTemplate').html();

  var tag = "#moveHistory ol";
  $(tag).html("");

  for (var j = this.moveId+1; j > 0; j--) {
    var color =  (j % 2) ? BLACK: WHITE;
    var additionalClass = "";
    if (this.moveIdViewed == j || (this.moveIdViewed == 0 && j == this.moveId+1)) {
      additionalClass = "moveViewed";
    }
    var move = $(template).bindTo({'color':COLOR_TEXT[color], 'additionalClass': additionalClass, 'move':j},{'fill':false});
    $(move).appendTo(tag);
  }
}


ListOfMoves.prototype.isViewing = function() {
  return this.moveIdViewed > 0;
}


function ListOfParticipants(elName) {
  this.elName = "#" + elName;
  this.participants = [];
}

ListOfParticipants.prototype.update = function(users, whitePlayer, blackPlayer) {
  var participants = [];
  for (var i = 0; i < users.length; i++) {
    participants[users[i].email] = users[i];
  }
  // todo: replace to style sheets
  var status = this.getUserStatus(participants[blackPlayer]);
  var rating = "n/a";
  if (participants[blackPlayer] != null) {
    rating = participants[blackPlayer].rating;
  }
  this.setPlayer("black", blackPlayer, rating, status);
  status = this.getUserStatus(participants[whitePlayer]);
  rating = "n/a";
  if (participants[whitePlayer] != null) {
    rating = participants[whitePlayer].rating;
  }
  this.setPlayer("white", whitePlayer, rating, status);

  var tag = $('#viewers ul');
  tag.html(""); // clear list
  for (var i = 0; i < users.length; i++) {
    if (users[i].email != whitePlayer && users[i].email != blackPlayer) {
      var status = this.getUserStatus(users[i]);
      this.setViewer(tag, users[i].email, users[i].rating, status);
    }
  }
}

ListOfParticipants.prototype.setPlayer = function(color, name, rating, status) {
  var template = $('#playerTemplate').html();

  var player = $(template).bindTo({name:name, rating:rating, status:status},{'fill':false});
  var tag = "#activePlayer." + color + " .player";
  $(tag).html("");
  $(player).appendTo(tag);
}

ListOfParticipants.prototype.setViewer = function (tag, name, rating, status) {
  var template = $('#viewerTemplate').html();

  var viewer = $(template).bindTo({name:name, rating:rating, status:status},{'fill':false});
  $(viewer).appendTo(tag);
}

ListOfParticipants.prototype.getUserStatus = function(user) {
      var status = "Loggedoff";
      if (user != null && user.active != null) {
        if (user.active == "false") {
          status = "Idle";
        } else {
          status = "Active";
        }
      }
      return status;
}

function Controller(canvasName, remotePlay) {
    this.board = null;
    this.render = null;
    this.view = null;
    this.state = NONE;
	this.canvasName = canvasName;
	this.validator = null;
	this.remotePlay = remotePlay;
	if (this.remotePlay == undefined) {
	  this.remotePlay = null;
	}
	this.playerColor = NULL_COLOR;
	this.whitePlayer = "";
	this.blackPlayer = "";
    this.alertTimerId = 0;
    this.title = "Crumble";
    document.title = this.title;
	_controller = this;
}

Controller.prototype.init = function(sizeController) {
  var elName = "#" + this.canvasName;
  this.elName = elName;
  this.view = null;
  this.sizeController = sizeController;
  $(elName).click(this.click);
  $(elName).dblclick(this.dblclick);
  $(elName).keydown(this.keydown);
  $(document).keydown(this.keydown);
  $(elName).keyup(this.keyup);
  $(elName).mousedown(this.mousedown);
  $(elName).mouseup(this.mouseup);
  $(elName).mousemove(this.mousemove);
  $(elName).mouseout(this.mouseout);
  $(elName).mouseover(this.mouseover);
  $(elName).scroll(this.scroll);
  $(elName).scrollLeft(this.scrollLeft);
  $(elName).scrollTop(this.scrollTop);
  //$(window).resize(this.onresize);
  if (sizeController == null) {
    window.onresize = this.onresize;
  }
  this.validator = new Validator();
  this.turnValidation();  // initialize validator
  if (this.remotePlay == null) {
    this.newGame(UNKNOWN);
  } else {
    this.remotePlay.init();
	this.showMessage("Select your color and click 'new game'");
    this.remotePlay.sendNewGameRequest(""); // see if the game was already started, then watch it
  }
}

Controller.prototype.setWhitePlayer = function(email) {
  this.whitePlayer = email;
}

Controller.prototype.setBlackPlayer = function(email) {
  this.blackPlayer = email;
}

Controller.prototype.getBoard = function() {
  return this.game.getBoard();
}

Controller.prototype.getPiece = function(e) {
  var realX = 1.0 * (this._getX(e) - this.view.boardStartX) / this.view.unitSize;
  var realY = 1.0 * (this._getY(e) - this.view.boardStartY) / this.view.unitSize;
  return this.getBoard().getPiece(realX, realY);
}

Controller.prototype._getX = function(e) {
  return e.pageX - e.currentTarget.offsetLeft;
}

Controller.prototype._getY = function(e) {
  return e.pageY - e.currentTarget.offsetTop;
}


Controller.prototype.redraw = function() {
  this.render.draw(this.getBoard());
  this.showMessage(this.game.getMessage());
  this.showMaterial();
}

Controller.prototype.showMessage = function(message) {
  if(message =="") {
    message = "OK";
  }
  $("#message").html(message);
}

Controller.prototype.showMaterial = function() {
  var materialBlack = this.game.getBoard().material(BLACK);
  var materialWhite = this.game.getBoard().x * this.game.getBoard().y - materialBlack;
  $("#material").html("Material: Black: " + materialBlack + "; White: " + materialWhite);
}

Controller.prototype.showWhoseMove = function() {
  if (this.game.gameOver) {
	$("#whoseMove").html("" + this.game.getMoveId() + ": " + "<b>" + COLOR_TEXT[this.game.getWinningColor()] + "</b>" + " won");
  } else {
    $("#whoseMove").html("" + this.game.getMoveId() + ": " + "<b>" + COLOR_TEXT[this.game.getCurrentColor()] + "</b>" + " to move");
  }
}

Controller.prototype.checkValid = function() {
  this.getBoard()._assertValid();
}

Controller.prototype.updateUsers = function(users) {
  if (!this.listOfParticipants) {
    this.listOfParticipants = new ListOfParticipants("participants");
  }
  this.listOfParticipants.update(users, this.whitePlayer, this.blackPlayer);
}

Controller.prototype.cancelMove = function() {
  if (_controller.remotePlay != null && _controller.playerColor != _controller.game.getCurrentColor()) {
    _controller.showMessage("Can not cancel the other player's move");
    return;
  }

  this.game.cancelMove();
  this.redraw();
  this.showWhoseMove();
}

Controller.prototype.leaveTable = function() {
  this.remotePlay.leaveTable();//OPPOSITE_COLOR[this.playerColor]);
}

Controller.prototype.makeRemoteMove = function(move) {
  assert(this.remotePlay != null && this.playerColor != this.game.getCurrentColor()); //XXX && this.game.getMoveId() > 1 || this.game.getMoveId() == 1);
  var msg = "New Move!";
  this.alertTimerId = setInterval(function() {
     document.title = document.title == msg ? _controller.title : msg;
  }, 1000);
  this.recreateMove(move);
}

Controller.prototype.recreateMove = function(move) {
  this.game.makeMoveFromRepresentation(move);
  this.redraw();
  this.showWhoseMove();
}


Controller.prototype.submitMove = function() {
  var success = this.game.submitMove();
  if (!success) {
    this.redraw();
    return;
  }
  // for remote play, submit move is called to complete opponents move - (player color does not match)
  // as well as for the self-move. In the latter case we need to send self move to the server
  // in the former case we just visually complete the move but don't do anything with the server
  if (this.remotePlay != null
      && this.playerColor == this.game.getLastPlayedColor()
      && this.moveToCatchup < this.game.getMoveId()-1) {
    this.remotePlay.sendMove(this.game.getLastMoveRepresentation(), this.game.getWinningColor());
  }
  this.listOfMoves.submitMove(this.game.moveId);
  this.redraw();
  this.showWhoseMove();
}

Controller.prototype.undo = function() {
  if (this.remotePlay != null) {
    this.showMessage("Undo is not supported for remote game");
    return;
  }
  assert(this.remotePlay == null);
  this.game.undoLastMove();
  this.listOfMoves.undoMove(this.game.moveId);
  this.redraw();
  this.showWhoseMove();
}

Controller.prototype.newGame = function(role, moveToCatchup) {
  this.moveToCatchup = moveToCatchup;
  if (role == UNKNOWN && this.remotePlay != null && this.game && !this.game.gameOver && this.game.getMoveId() > 1) {
    this.showMessage("Current game is not over. Can not start a new game");
    return;
  }
  if (this.remotePlay != null && role == PLAY_WHITE) {
     this.playerColor = WHITE;
  } else if (this.remotePlay != null && role == PLAY_BLACK) {
     this.playerColor = BLACK;
  } else if (this.remotePlay != null) {
    this.playerColor = NULL_COLOR;
  }

  if (this.remotePlay == null || this.remotePlay != null && role != UNKNOWN) {
	  var whiteComp = false;
	  var blackComp = false;
	  if (this.remotePlay == null) {
	    whiteComp = $("#whitePlayerComp")[0].checked;
	    blackComp = $("#blackPlayerComp")[0].checked;
	  }
	  this.game = new Game(6, 6, this.validator, whiteComp, blackComp);
	  if (this.remotePlay == null) {
	    this.setDemo();
	  }
	  var unitSize = $(this.elName).height() / this.getBoard().y;
	  if (this.view == null) {
		  this.view = new View(unitSize, 0, 0, $(this.elName).width(), $(this.elName).height())
		  var out = new RealOutput(this.view, this.canvasName);
		  this.render = new Render(out, this.view);
	  }
		  /*
		  if (_controller.sizeController == null) {
		    setTimeout('_controller.onresize()',20);
            this.onresize();
          } else {
            setTimeout('_controller.sizeController.onresize()', 20);
            _controller.sizeController.onresize();
          }*/
      this.redraw();
	  this.showWhoseMove();
	  this.listOfMoves = new ListOfMoves("moveHistory");
	  //if (role == PLAY_WHITE || role == WATCH || _controller.moveToCatchup > 1) {
	  if (this.remotePlay != null) {
	    this.remotePlay.startListeningForNextMove(1);
	  }
	  if (this.remotePlay != null ) {
	    $("#newGameWhite").toggle(false);
	    $("#newGameBlack").toggle(false);
	  }
  }
  if (this.remotePlay != null && role == UNKNOWN) {
    if (!$("#desiredBlack")[0].checked) {
      desiredColor = COLOR_TEXT[WHITE];
    } else {
      desiredColor = COLOR_TEXT[BLACK];
    }
    this.showMessage("Sending new game request for " + desiredColor + "... Please wait.");
    this.remotePlay.sendNewGameRequest(desiredColor);
  }
}

Controller.prototype.requestNewGame = function(color) {
  $("#desiredBlack")[0].checked = (color == BLACK);
  this.newGame(UNKNOWN);
}

Controller.prototype.snapView  = function() {
  if (this.view == null) {
    return;
  }
  this.view.unitSize = $(this.elName).height() / this.getBoard().y;
  this.view.boardStartX = 0;
  this.view.boardStartY = 0;
  this.view.windowWidth = $(this.elName).width();
  this.view.windowHeight = $(this.elName).height();
  this.redraw();
}

Controller.prototype.turnValidation = function() {
  if (this.remotePlay != null) {
    this.validator.enable(true);
  } else {
	  this.validator.enable($("#validator")[0].checked);
	  this.showMessage((this.validator.isEnabled())?"Enabling validation...":"Validation is off");
  }
}

Controller.prototype.setDemo = function() {
  assert(this.remotePlay == null);
  var demo = $("#demo")[0].checked;
  if (demo) {
    $("#blackPlayerComp")[0].checked = true;
    $("#whitePlayerComp")[0].checked = true;

    setTimeout("_controller.game.setDemoMode(true)", 100);
  }
}

Controller.prototype.setBlackPlayerComp = function() {
  assert(this.remotePlay == null);
  this.game.setPlayerComp(BLACK, $("#blackPlayerComp")[0].checked);
  this.redraw();
}

Controller.prototype.setWhitePlayerComp = function() {
  assert(this.remotePlay == null);
  this.game.setPlayerComp(WHITE, $("#whitePlayerComp")[0].checked);
  this.redraw();
}

Controller.prototype.click = function(e) {
  if (_controller.listOfMoves.isViewing()) {
    _controller.showMessage("View only mode");
    return;
  }
  if (_controller.remotePlay != null &&
     (_controller.playerColor != _controller.game.getCurrentColor())
     || (_controller.moveToCatchup > _controller.game.getMoveId())) {
    _controller.showMessage("Please wait for the other player to make a move");
    return;
  }
  var piece = _controller.getPiece(e);
  if (piece) {
    _controller.game.swap(piece);
    _controller.redraw();
  }
}

Controller.prototype.onresize=function(e) {
  //var y = window.innerHeight-40;
  //var x = window.innerWidth-40;

  var y = $(window).height()/2;
  var x = $(window).width()/2;
  var size = y;
  if (x < y) {
	    size = x;
  }
  $(_controller.elName).width(size);
  $(_controller.elName).height(size);
  _controller.snapView();
}

Controller.prototype.dblclick= function(e) {
}

Controller.prototype.keydown= function(e) {
/*
  if (_controller && _controller.listOfMoves) {
	  var id = _controller.listOfMoves.getViewedMoveId();
	  if (e.keyCode == 80) { // (p)revious
	    if (id > 0) {
	      id -= 1;
	    } else {
	      id = _controller.game.getMoveId() - 1;
	    }
	    _controller.onMoveSelected(id);
	  } else if (e.keyCode == 78) { //(n)ext
	    if (id < _controller.game.getMoveId() - 1) {
	      id += 1;
	    } else {
	      id = 0;
	    }
	    _controller.onMoveSelected(id);
	  } else if (e.keyCode == 73) { // i(interrupt)
	    error;
	  }
   }
 */
}

Controller.prototype.keypress= function(e) {
}

Controller.prototype.keyup= function(e) {

}

Controller.prototype.mousedown= function(e) {
  _controller.pieceStart = _controller.getPiece(e);
  if (_controller.pieceStart) {
    if (e.ctrlKey || e.shiftKey) {
	  if (e.ctrlKey && !e.shiftKey) {
	    _controller.state = ZOOM;
	  } else if (e.shiftKey && !e.ctrlKey) {
	    _controller.state = SHIFT;
	  }
    }

    _controller.clickX = _controller._getX(e);
    _controller.clickY = _controller._getY(e);
    _controller.oldX = _controller._getX(e);
    _controller.oldY = _controller._getY(e);
  }
}


Controller.prototype.mouseup = function(e) {
  if (_controller.state != NONE) {
      _controller.state = NONE;
      return;
  }
  if (_controller.remotePlay != null && _controller.playerColor != _controller.game.getCurrentColor()) {
    _controller.showMessage("Please wait for the other player to make a move");
    _controller.pieceStart = null;
    _controller.pieceEnd = null;
    return;
  }
  if (_controller.listOfMoves.isViewing()) {
    _controller.showMessage("View only mode");
    _controller.pieceStart = null;
    _controller.pieceEnd = null;
    return;
  }
  _controller.pieceEnd = _controller.getPiece(e);
  var deltaX = _controller._getX(e) - _controller.clickX;
  if (deltaX < 0) deltaX = -deltaX;
  var deltaY = _controller._getY(e) - _controller.clickY;
  if (deltaY < 0) deltaY = -deltaY;
  if (deltaX > 2 || deltaY > 2) {

	  if (_controller.pieceEnd == _controller.pieceStart) {
		  var dir = (deltaX > deltaY)? HORIZONTAL : VERTICAL;
		  if (_controller.pieceEnd) {
		    _controller.game.splitOrSwap(_controller.pieceEnd, dir);
		    _controller.redraw();
		  }
	  } else {
	    var merge = e.ctrlKey && e.shiftKey;
	    var pFirst = _controller.pieceStart;
	    var pLast = _controller.getPiece(e);
	    if (!pFirst || !pLast) return;
	    if (!pFirst.start[HORIZONTAL].smaller(pLast.end[HORIZONTAL]) ||
	        !pFirst.start[VERTICAL].smaller(pLast.end[VERTICAL])) {
	        var temp = pFirst;
	        pFirst = pLast;
	        pLast = temp;
        }
	    if (merge) {
		    var small = new Rational(1, 2048);
		    var left = (pFirst.start[HORIZONTAL].smaller(pLast.start[HORIZONTAL]))?pFirst.start[HORIZONTAL] : pLast.start[HORIZONTAL];
		    var right = (pFirst.end[HORIZONTAL].bigger(pLast.end[HORIZONTAL]))?pFirst.end[HORIZONTAL] : pLast.end[HORIZONTAL];
		    var top = (pFirst.start[VERTICAL].smaller(pLast.start[VERTICAL]))?pFirst.start[VERTICAL] : pLast.start[VERTICAL];
		    var bottom = (pFirst.end[VERTICAL].bigger(pLast.end[VERTICAL]))?pFirst.end[VERTICAL] : pLast.end[VERTICAL];

		    var small = 0.00001;
		    var pLeftTop = _controller.getBoard().getPiece(left.float() + small, top.float() + small );
		    var pLeftBottom = _controller.getBoard().getPiece(left.float() + small, bottom.float()- small);
		    var pRightTop = _controller.getBoard().getPiece(right.float() - small, top.float() + small);
		    var pRightBottom = _controller.getBoard().getPiece(right.float() - small, bottom.float() - small);
            _controller.game.merge(pLeftTop, pRightTop, pLeftBottom, pRightBottom);
            _controller.redraw();
        } else {
          var dir = UNKNOWN_DIR;
          if (pFirst.canSplit(VERTICAL) && pLast.canSplit(VERTICAL) && pFirst.getMiddle(VERTICAL).equals(pLast.getMiddle(VERTICAL))) {
            dir = VERTICAL;
          } else if (pFirst.canSplit(HORIZONTAL) && pLast.canSplit(HORIZONTAL) && pFirst.getMiddle(HORIZONTAL).equals(pLast.getMiddle(HORIZONTAL))) {
            dir = HORIZONTAL;
          }
          if (dir != UNKNOWN_DIR) {
	          _controller.game.longSplit(pFirst, pLast, dir);
	          _controller.redraw();
          }
	    }
	  }
	  _controller.oldX = _controller._getX(e);
	  _controller.oldY = _controller._getY(e);
  }
  _controller.pieceStart = null;
  _controller.pieceEnd = null;
}

Controller.prototype.mousemove = function(e) {
  if (_controller.remotePlay) {
    _controller.remotePlay.remoteUser.onActivityDetected(false);
  }
  if (_controller.alertTimerId) {
    clearInterval(_controller.alertTimerId);
    _controller.alertTimerId = 0;
    document.title = _controller.title;
  }
  if (!_controller.pieceStart) {
    return;
  }
  if (_controller.state == ZOOM) {
    var oldUnitSize = _controller.view.unitSize;
    _controller.view.unitSize += 1.0 * _controller.view.unitSize * (_controller._getX(e) - _controller.clickX) / 800;
    if (_controller.view.unitSize < MIN_UNIT_SIZE) {
      _controller.view.unitSize = MIN_UNIT_SIZE;
    }
    var newBoardSizeX = _controller.getBoard().x * _controller.view.unitSize;
    var newBoardSizeY = _controller.getBoard().y * _controller.view.unitSize;
    _controller.view.boardStartX = _controller.clickX - (_controller.clickX - _controller.view.boardStartX) * _controller.view.unitSize / oldUnitSize;
    _controller.view.boardStartY = _controller.clickY - (_controller.clickY - _controller.view.boardStartY) * _controller.view.unitSize / oldUnitSize;
    _controller.redraw();
  } else if (_controller.state == SHIFT) {
    _controller.view.boardStartX += (_controller._getX(e) - _controller.oldX);
    _controller.view.boardStartY += (_controller._getY(e) - _controller.oldY);
    _controller.redraw();
  }
  _controller.oldX = _controller._getX(e);
  _controller.oldY = _controller._getY(e);
}

Controller.prototype.mouseout= function(e) {

}

Controller.prototype.mouseover = function(e) {

}

Controller.prototype.scroll= function(e) {

}

Controller.prototype.scrollLeft= function(e) {
}

Controller.prototype.scrollTop= function(e) {
}

Controller.prototype.onMoveSelected = function(v) {
  if (this.game.getMoveId() == v) {
    v = 0;
  }
  this.listOfMoves.setMoveSelected(v);
  this.game.setMoveToView(v);
  this.redraw();
}